/*
 * lir.c
 *
 * Implements a lower-level intermediate representation
 */

#include "lir.h"

struct lir_t *lbbl_new(struct ir_t *);

int lblockID = 2; // Counter for basic block IDs

void lir_map_ir(struct ir_t *block) {
	struct tac_t *stmt;
	struct lir_t *lblock = lbbl_new(block);
	
	if(block->cond != NULL) // Is it a conditional branch?
        {
			for(stmt = block->statements; stmt != NULL; stmt = stmt->next)
				if(stmt->next != NULL)
					tac_to_mc(stmt, lblock, 1);	// translate three address code to MIPS code
				else	// last statement, branch
					tac_to_mc(stmt, lblock, 0);	// translate three address code to MIPS code
        }
	else if(block->child == NULL)
		{
			for(stmt = block->statements; stmt != NULL; stmt = stmt->next)
				 tac_to_mc(stmt, lblock, 1);	// translate three address code to MIPS code
		}
	else if(block->next != block->child) // Does it need to jump anyway?
        {
			for(stmt = block->statements; stmt != NULL; stmt = stmt->next)
			{	
				tac_to_mc(stmt, lblock, 1);	// translate three address code to MIPS code
				
				if(stmt->next != NULL)	// last statement, add jump after that
					tac_to_mc(stmt, lblock, 2);	// translate three address code to MIPS code
			}
        }
}

/* ----------------------------------------------------------------------- 
 * Constructs a lir basic block
 * ----------------------------------------------------------------------- 
 */
struct lir_t *lbbl_new(struct ir_t *block) {
	struct lir_t *lbbl = malloc(sizeof(struct lir_t)), *temp, *parent;
	lbbl->lid = block->id;
	// Empty the lists
	lbbl->instruction = NULL;
	lbbl->numbered = 0;
	lbbl->stack = NULL;
		// add to global lir list
	add_to_global_lir_list(lbbl);	
	return lbbl;
}

void add_to_global_lir_list(struct lir_t *lir) {
	struct lir_list_t *temp = global_lir_list;
	if (temp->lir == NULL) {
		global_lir_list->lir = lir;
	} else {
		struct lir_list_t *newList = malloc(sizeof(struct lir_list_t));
		while (temp->next != NULL) {
			temp = temp->next;
		}
		temp->next = newList;
		newList->lir = lir;
	}	
}

struct lir_list_t *global_lir_list_init() {
	struct lir_list_t *ret = malloc(sizeof(struct lir_list_t));
	return ret;
}

// type 0: branch; type 1: nothing;	type 2: jump
void tac_to_mc(struct tac_t *stmt, struct lir_t *lblock, int type) {
	char *temp_rs;
	char *temp_rt;
	char *temp_rd;
	int  temp_imm;
	char *temp_label;
	
	if(type == 0)
	{
		// TODO: find offset of v1
		// temp_imm = symtab_variable_lookup(v1);
		// load v1 to $t1
		// add instruction lw	$t1, imm($fp)
		temp_rs = "$fp";
		temp_rt = "$t1";
		instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);
		
		// TODO: find offset of v2
		// temp_imm = symtab_variable_lookup(v2);
		// load v2 to $t2
		// add instruction lw	$t2, imm($fp)
		temp_rs = "$fp";
		temp_rt = "$t2";
		instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);

		// add instruction beq	$t1, $t2, target
		temp_label = block->child->id;	// target
		temp_rs = "$t1";
		temp_rt = "$t2";
		instruction_new(*lblock->instruction, MIPS_BRANCH, temp_rs, temp_rt, NULL, NULL, temp_label);
	}
	else if(type == 1)
	{
		switch(stmt->type) {
			case TAC_ENTER:
				// TODO: change $fp to the new function frame pointer
			break;
			case TAC_RETURN:
				// TODO: change $fp back
				// TODO: change $ra back
			break;
			case TAC_SET:
				temp_rs = "$zero";
				temp_rt = "$t0";			
				temp_imm = stmt->n2;
				// add instruction addi	$t0, $zero, temp_imm
				instruction_new(*lblock->instruction, MIPS_ADD_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
				// TODO: find offset of v3
				// save $t0 to v3
				// add instruction sw	$t0, v3_offset($fp)
			break;
			case TAC_EQUAL: 
			break;
			case TAC_NOTEQ: 
			break;
			case TAC_LT: 
			break;
			case TAC_GT: 
			break;
			case TAC_LEQ: 
			break;
			case TAC_GEQ: 
			break;
			case TAC_PLUS:
				// TODO: find offset of v1
				// temp_imm = symtab_variable_lookup(v1);
				// load v1 to $t1
				// add instruction lw	$t1, imm($fp)
				temp_rs = "$fp";
				temp_rt = "$t1";
				instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);
				
				if(stmt->v2!= NULL)
				{
					// TODO: find offset of v2 if v2 is variable
					// temp_imm = symtab_variable_lookup(v2);
					// load v2 to $t2
					// add instruction lw	$t2, v2_offset($fp)
					temp_rs = "$fp";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);
					
					// add instruction add	$t0, $t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					temp_rd = "$t0";
					instruction_new(*lblock->instruction, MIPS_ADD, temp_rs, temp_rt, temp_rd, NULL, NULL);
				}
				else
				{	
					temp_rs = "$t1";
					temp_rt = "$t0";
					temp_imm = stmt->n2;
					// add instruction addi	$t0, $t1, temp_imm
					instruction_new(*lblock->instruction, MIPS_ADD_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
				}
				// TODO: find offset of v3
				// temp_imm = symtab_variable_lookup(v3);
				// save $t0 to v3
				// add instruction sw	$t0, v3_offset($fp)
				temp_rs = "$fp";
				temp_rt = "$t0";
				instruction_new(*lblock->instruction, MIPS_STORE_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);
				break;
			case TAC_MINUS:
				// TODO: find offset of v1
				// temp_imm = symtab_variable_lookup(v1);
				// TODO: load v1 to $t1
				// add instruction lw	$t1, imm($fp)
				temp_rs = "$fp";
				temp_rt = "$t1";
				instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm);
				
				if(stmt->v2!= NULL)
				{
					// TODO: find offset of v2 if v2 is variable
					// temp_imm = symtab_variable_lookup(v2);
					// load v2 to $t2
					// add instruction lw	$t2, v2_offset($fp)
					temp_rs = "$fp";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm);
					// add instruction sub	$t0, $t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					temp_rd = "$t0";
					instruction_new(*lblock->instruction, MIPS_SUBTRACT, temp_rs, temp_rt, temp_rd, NULL);
				}
				else
				{	
					temp_rs = "$t1";
					temp_rt = "$t0";
					temp_imm = (stmt->n2) * (-1);
					// add instruction addi	$t0, $t1, temp_imm
					instruction_new(*lblock->instruction, MIPS_ADD_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
				}
				// TODO: find offset of v3
				// temp_imm = symtab_variable_lookup(v3);
				// save $t0 to v3
				// add instruction sw	$t0, v3_offset($fp)
				temp_rs = "$fp";
				temp_rt = "$t0";
				instruction_new(*lblock->instruction, MIPS_STORE_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);
				break;
			case TAC_OR:
				// TODO: find offset of v1
				// temp_imm = symtab_variable_lookup(v1);
				// TODO: load v1 to $t1
				// add instruction lw	$t1, imm($fp)
				temp_rs = "$fp";
				temp_rt = "$t1";
				instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm);

				if(stmt->v2!= NULL)
				{
					// TODO: find offset of v2
					// temp_imm = symtab_variable_lookup(v2);
					// TODO: load v2 to $t2
					// add instruction lw	$t2, imm($fp)
					temp_rs = "$fp";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_LOAD_WORD, temp_rs, temp_rt, NULL, temp_imm);
					
					// add instruction or	$t0, $t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					temp_rd = "$t0";
					instruction_new(*lblock->instruction, MIPS_OR, temp_rs, temp_rt, temp_rd, NULL, NULL);
				}
				else
				{	
					temp_rs = "$t1";
					temp_rt = "$t0";
					temp_imm = stmt->n2;
					// add instruction ori	$t0, $t1, temp_imm
					instruction_new(*lblock->instruction, MIPS_OR_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
				}
				// TODO: find offset of v3
				// temp_imm = symtab_variable_lookup(v3);
				// save $t0 to v3
				// add instruction sw	$t0, v3_offset($fp)
				temp_rs = "$fp";
				temp_rt = "$t0";
				instruction_new(*lblock->instruction, MIPS_STORE_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);
				break;
			case TAC_TIMES:
				// TODO: find offset of v1
				// load v1 to $t1
				// add instruction lw	$t1, v1_offset($fp)
				if(stmt->v2!= NULL)
				{
					// TODO: find offset of v2 if v2 is variable
					// load v2 to $t2
					// add instruction lw	$t2, v2_offset($fp)
					
					// add instruction mult	$t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_MULT, temp_rs, temp_rt, NULL, NULL, NULL);
				}
				else
				{
					temp_rs = "$zero";
					temp_rt = "$t2";			
					temp_imm = stmt->n2;
					// add instruction addi	$t2, $zero, temp_imm
					instruction_new(*lblock->instruction, MIPS_ADD_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
					// add instruction mult	$t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_MULT, temp_rs, temp_rt, NULL, NULL, NULL);
				}
				// Note: we define integer is 32 bit in gen_backend_asm
				// add instruction mflo	$t0
				temp_rd = "$t0";
				instruction_new(*lblock->instruction, MIPS_MFLO, NULL, NULL, temp_rd, NULL, NULL);
				// TODO: find offset of v3
				// save $t0 to v3
				// add instruction sw	$t0, v3_offset($fp)		
			break;
			case TAC_DIV:
				// TODO: find offset of v1
				// load v1 to $t1
				// add instruction lw	$t1, v1_offset($fp)
				if(stmt->v2!= NULL)
				{
					// TODO: find offset of v2 if v2 is variable
					// load v2 to $t2
					// add instruction lw	$t2, v2_offset($fp)
					
					// add instruction div	$t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_DIV, temp_rs, temp_rt, NULL, NULL, NULL);
				}
				else
				{
					temp_rs = "$zero";
					temp_rt = "$t2";			
					temp_imm = stmt->n2;
					// add instruction addi	$t2, $zero, temp_imm
					instruction_new(*lblock->instruction, MIPS_ADD_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
					// add instruction div	$t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					instruction_new(*lblock->instruction, MIPS_DIV, temp_rs, temp_rt, NULL, NULL, NULL);
				}
				// Note: we define integer is 32 bit in gen_backend_asm
				// add instruction mflo	$t0
				temp_rd = "$t0";
				instruction_new(*lblock->instruction, MIPS_MFLO, NULL, NULL, temp_rd, NULL, NULL);
				// TODO: find offset of v3
				// temp_imm = symtab_variable_lookup(v3);
				// save $t0 to v3
				// add instruction sw	$t0, temp_imm($fp)
				temp_rs = "$fp";
				temp_rt = "$t0";
				instruction_new(*lblock->instruction, MIPS_STORE_WORD, temp_rs, temp_rt, NULL, temp_imm, NULL);			
			break;
			case TAC_MOD: 
			break;
			case TAC_AND:
				// TODO: find offset of v1
				// load v1 to $t1
				// add instruction lw	$t1, v1_offset($fp)
				if(stmt->v2!= NULL)
				{
					// TODO: find offset of v2 if v2 is variable
					// load v2 to $t2
					// add instruction lw	$t2, v2_offset($fp)
					
					// add instruction and	$t0, $t1, $t2
					temp_rs = "$t1";
					temp_rt = "$t2";
					temp_rd = "$t0";
					instruction_new(*lblock->instruction, MIPS_AND, temp_rs, temp_rt, temp_rd, NULL, NULL);
				}
				else
				{	
					temp_imm = stmt->n2;
					// add instruction andi	$t0, $t1, temp_imm
					instruction_new(*lblock->instruction, MIPS_AND_I, temp_rs, temp_rt, NULL, temp_imm, NULL);
				}
				// TODO: find offset of v3
				// save $t0 to v3
				// add instruction sw	$t0, v3_offset($fp)		
			break;
			case TAC_NEGATE:
				// this can be performed with two instructions:
				// setting a register's value to -1 ($t1)
				// using xor on the register to be negated and the register with -1 in it
			break;
			case TAC_NOT: 
			break;
			default: break;
				}
	}
	else	// type == 2
	{
		// add instruction j	target
		temp_label = block->child->id;	// target
		instruction_new(*lblock->instruction, MIPS_JUMP, NULL, NULL, NULL, NULL, temp_label);
	}
}

/* ----------------------------------------------------------------------- 
 * Appends a MIPS instruction to the lir
 * ----------------------------------------------------------------------- 
 */
void instruction_new(struct mc_t **ss, int opcode, char *rs, 
                     char *rt, char *rd, int imm, char *label) 
    {
    struct mc_t *instruction = *ss;
    if(instruction == NULL) /* Is the list of statements empty? */
        {
        instruction = malloc(sizeof(struct mc_t)); /* Put the statement in
                                                      the list */
        }
    else 
        {
        while (instruction->next != NULL) 
            instruction = instruction->next; /* Find the end of the list */

        instruction->next = malloc(sizeof(struct mc_t)); /* Append the statement */
        instruction = instruction->next;
        }
    instruction->opcode = opcode;
    instruction->rs = rs;
    instruction->rt = rt;
    instruction->rd = rd;
    instruction->imm = imm;
	instruction->label = label;
    }
